﻿namespace Ngs.Data
{
    #region Using Directives

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    #endregion

    /// <summary>
    /// Base class for IQueryable implementation.
    /// </summary>
    /// <remarks>Overrides Query operators as prescribed in .NET Guidelines.</remarks>
    /// <typeparam name="T"></typeparam>
    public abstract class DataQuery<T> : IDataQuery<T>, IQueryable<T>, IEnumerable<T>, IEnumerable, IOrderedQueryable<T>, IOrderedQueryable
    {

        protected DataQuery()
        {
        }

        #region Properties

        public Type ElementType
        {
            get { return typeof(T); }
        }

        public abstract Expression Expression
        {
            get;
        }

        public virtual IQueryProvider Provider
        {
            get { throw new NotSupportedException(); }
        }

        protected List<string> Includes { get; set; }

        #endregion

        #region IEnumerable<T> Members

        public abstract IEnumerator<T> GetEnumerator();

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion

        /// <summary>
        /// Include operator is supported by the ObjectQuery Entity Framework.
        /// It provides a means of eager loading an associated object.
        /// </summary>
        /// <param name="includeMember"></param>
        /// <returns></returns>
        public IDataQuery<T> Include<M>(Expression<Func<T, M>> includeMember)
        {
            MemberExpression member = (MemberExpression)includeMember.Body;
            string includeObject = member.Member.Name;
            ExceptionFactory.ThrowIfArgumentNullOrEmpty(includeObject, "func", "Expression did not yield a value.");
            this.Include(includeObject);
            return this;
        }

        #region Linq Operators

        public abstract T First();

        public abstract T First(Expression<Func<T, bool>> predicate);

        public abstract IDataQuery<IGrouping<TKey, T>> GroupBy<TKey>(Expression<Func<T, TKey>> keySelector);

        /// <summary>
        /// Filters the elements of an IEnumerable based on a specified type.
        /// Limits the query to only results of a specific type.
        /// </summary>
        /// <see cref="http://msdn.microsoft.com/en-us/library/bb360913.aspx"/>
        /// <seealso cref="http://msdn.microsoft.com/en-us/library/bb301729.aspx"/>
        /// <typeparam name="TKey"></typeparam>
        /// <returns></returns>
        public abstract IDataQuery<TKey> OfType<TKey>();

        public virtual IDataQuery<T> OfType()
        {
            return this;
        }

        public abstract IDataQuery<T> OrderBy<TKey>(Expression<Func<T, TKey>> keySelector);

        public abstract IDataQuery<T> OrderByDescending<TKey>(Expression<Func<T, TKey>> keySelector);

        public abstract IDataQuery<T> Skip(int count);

        public abstract IDataQuery<T> Reverse();

        public abstract IDataQuery<T> Take(int count);

        public abstract IDataQuery<T> Where(Expression<Func<T, bool>> predicate);

        public abstract int Count();

        #endregion

        public override string ToString()
        {
            if (this.Expression.NodeType == ExpressionType.Constant &&
                ((ConstantExpression)this.Expression).Value == this)
            {
                return "Query(" + typeof(T) + ")";
            }
            return this.Expression.ToString();
        }

        protected virtual void Include(string subObject)
        {
            this.Includes = this.Includes ?? new List<string>(4);
            int count = this.Includes.Count;
            int capacity = this.Includes.Capacity;
            if (count == capacity)
            {
                this.Includes.Capacity = capacity * 2;
            }
            this.Includes.Add(subObject);
        }
    }

}
